﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace Common.Utilities.TextMatch
{
    /// <summary>
    /// 
    /// </summary>
    public class ExSoundexConverter : PhoneticConverter
    {

        //Shoudl a miracode encoding be used?
        private bool miraCode;

        /// <summary>
        /// 
        /// </summary>
        public ExSoundexConverter() : this(false) { }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="resultLength"></param>
        public ExSoundexConverter(int resultLength) : this(resultLength, false) { }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="miraCode"></param>
        public ExSoundexConverter(bool miraCode)
            : base()
        {
            this.miraCode = miraCode;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="resultLength"></param>
        /// <param name="miraCode"></param>
        public ExSoundexConverter(int resultLength, bool miraCode)
            : base(resultLength)
        {
            this.miraCode = miraCode;
        }

        //Encodes a value using the Extended Soundex algorithm.
        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override string Convert(string value)
        {
            value = StringUtil.RemoveNonAlphaNumericCharacters(value);
            //Ensure the parameter is valid
            Debug.Assert(value != null, "Unexpected null value.", "The ExSoundexConverter.Convert(String) method requires a value.");

            if (value == null)
                throw new ArgumentNullException("value");

            //Validate input
            if (!(new AlphabeticValidator().Validate(value)))
                throw new ArgumentOutOfRangeException("value", value, NamespaceUtility.Resource["Error.InvalidPhoneticInput"]);
            else
                return (DoSoundex(value.ToUpper(CultureInfo.InvariantCulture), this.ResultLength, this.MiraCode, true));
        }

        /// <summary>
        /// 
        /// </summary>
        public bool MiraCode
        {
            get { return this.miraCode; }
            set { this.miraCode = value; }
        }

        //Carry out the Soundex encoding.
        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="maxResultLength"></param>
        /// <param name="miraCode"></param>
        /// <param name="encodeFirstCharacter"></param>
        /// <returns></returns>
        protected string DoSoundex(string value, int maxResultLength, bool miraCode, bool encodeFirstCharacter)
        {
            //Ensure the value parameter is valid
            Debug.Assert(value != null, "Unexpected null value.", "The ExSoundexConverter.DoSoundex(String) method requires a value.");

            if (value == null)
                throw new ArgumentNullException("value");

            string encodableValue;
            int lastCharvalue;

            if (encodeFirstCharacter)
            {
                encodableValue = value;
                lastCharvalue = 0;
            }
            else
            {
                encodableValue = value.Substring(1, value.Length - 1);
                lastCharvalue = EncodeChar(value[0]);
            }

            StringBuilder encodingBuilder = new StringBuilder();
            foreach (char currentChar in encodableValue)
            {
                int currentCharvalue = EncodeChar(currentChar);
                if (currentCharvalue > 0 && (currentCharvalue != lastCharvalue))
                {
                    encodingBuilder.Append(currentCharvalue.ToString(CultureInfo.InvariantCulture));
                }

                //Store character code for comparison in next iteration
                //after doing the consonant seperator check!
                if (!((currentChar == 'W' || currentChar == 'H') && miraCode))
                    lastCharvalue = currentCharvalue;

                if (encodingBuilder.Length == maxResultLength) break;
            }

            // Pad with zeros
            for (int iLoopVar = encodingBuilder.Length; iLoopVar < maxResultLength; iLoopVar++)
            {
                encodingBuilder.Append("0");
            }

            return encodingBuilder.ToString();
        }

        private int EncodeChar(char c)
        {
            switch (c)
            {
                case 'B':
                case 'F':
                case 'P':
                case 'V':
                    return 1;
                case 'C':
                case 'G':
                case 'J':
                case 'K':
                case 'Q':
                case 'S':
                case 'X':
                case 'Z':
                    return 2;
                case 'D':
                case 'T':
                    return 3;
                case 'L':
                    return 4;
                case 'M':
                case 'N':
                    return 5;
                case 'R':
                    return 6;
                default:
                    return 0;
            }
        }

    }
}
